package org.dbpedia.extraction.mappings

import java.text.{NumberFormat, ParsePosition}

import org.dbpedia.extraction.destinations.Graph
import org.dbpedia.extraction.wikiparser._
import collection.mutable.HashMap
import collection.mutable.HashSet

/**
 * @author Jonas Brekle <jonas.brekle@gmail.com>
 * @author Sebastian Hellmann <hellmann@informatik.uni-leipzig.de>
 */

class TestExtractor extends Extractor {
  //private val language = extractionContext.language.wikiCode
  private val language = "de"

  private val possibleLanguages = Set("en", "de")
  require(possibleLanguages.contains(language))

  // a wiktionary page has this structure (example)
  /**
   | ROOT LANG     POS   CS       SE
   +-------------------------------------------
   | page
   |      english
   |               noun
   |                     meaning
   |                              x is a y
                         examples
   |                     ...
   |               verb
   |               ...
   |      german
   +--------------------------------------------
   |
   |  LANG = languages
   |  POS = part of speech
   |  CS = content sections
   |  SE = section elements
   |
   */
  //on the top level: usages in different languages are separated by a SectionNode containing a TemplateNode with these titles
  private val languageTemplates = Map(
    "de" -> "Sprache",
    "en" -> "language"
  )
  //on the second level: usages as different parts of speech are separated by a TemplateNode with these titles
  private val partOfSpeechTemplates = Map(
    "de" -> "Wortart",
    "en" -> "fixme"
  )


  private val translatePOS = Map(
    "noun" -> Map(
      "de" -> "Substantiv",
      "en" -> "noun",
      "uri"-> "http://some-POS-ontology.org/noun"
    ),
    "verb" -> Map(
      "de" -> "Verb",
      "en" -> "Verb"
    ),
    "adverb" -> Map(
      "de" -> "Adverb",
      "en" -> "Adverb"
    ),
    "adjective" -> Map(
      "de" -> "Adjektiv",
      "en" -> "Adjective"
    ),
    "pronoun" -> Map(
      "de" -> "Pronom",
      "en" -> "Pronoun"
    ),
    "cardinalNumber" -> Map(
      "de" -> "Kardinalzahl",
      "en" -> "Cardinal number"
    )   //TODO complete
  )

  //on the third level: these TemplateNodes with these titles are used to separate the actual data
  private val translateSection = Map(
    "meaning" -> Map(
      "de" -> "Bedeutungen",
      "en" -> "///extraction///none///1"  //directly follows the POS tag
    ),
    "example" ->  Map(
      "de" -> "Beispiele",
      "en" -> "///extraction///none///2"  // directly follows each meaning
    ),
    "pronunciation" ->  Map(
      "de" -> "Aussprache",
      "en" -> "Pronunciation"
    ),
    "antonym" ->  Map(
      "de" -> "Gegenwörter",
      "en" -> "Antonyms"
    ),
    "synonym" ->  Map(
      "de" -> "Synonyme",
      "en" -> "Synonyms"
    ),
    "hyperonym" ->  Map(
      "de" -> "Oberbegriffe",
      "en" -> "Hyperonyms"
    ),
    "inflection" ->  Map(
      "de" -> "???",
      "en" -> "inflections"
    ),
    "usage" ->  Map(
      "de" -> "Charakteristische Wortkombinationen",
      "en" -> "///extraction///none///3"
    ),
    "derivedTerm" ->  Map(
      "de" -> "Abgeleitete Begriffe",
      "en" -> "Derived terms"
    ),
    "relatedTerm" ->  Map(
      "de" -> "Ähnlichkeiten",
      "en" -> "///extraction///none///3"
    ),
    "translation" ->  Map(
      "de" -> "Übersetzungen",
      "en" -> "Translations"
    ),
    "etymology" ->  Map(
      "de" -> "Etymologie",
      "en" -> "Etymology"
    ),
    "note" ->  Map(
      "de" -> "Anmerkungen",
      "en" -> "Usage notes"
    ),
    "anagram" ->  Map(
      "de" -> "Anagramme",
      "en" -> "Anagrams"
    ),
    "abreviationFor" ->  Map(
      "de" -> "Abkürzungen",
      "en" -> "Abbreviation"
    ),
    "abreviationOf" ->  Map(
      "de" -> "Herkunft",
      "en" -> "Abbreviation"
    ),
    "hyphenation" ->  Map(
      "de" -> "Silbentrennung",
      "en" -> "Hyphenation"
    ),
    "seeAlso" ->  Map(
      "de" -> "Siehe auch",
      "en" -> "See also"
    )
  )

  //TODO do this on a single line with scala magic :)
  private var translateSectionBack : scala.collection.mutable.Map[String, scala.collection.mutable.Map[String,String]] =  scala.collection.mutable.Map()
  for(lang <- possibleLanguages){
    translateSectionBack += lang -> scala.collection.mutable.Map()
  }
  for(key <- translateSection.keySet){
    for(lang <- possibleLanguages){
       translateSectionBack.apply(lang) += translateSection.apply(key).apply(lang) ->  key
    }
  }

  //some context storage
  private var isRecordingSectionElement = false
  private var skipThisOneFromRecording = false

  private var currSection = ""
  private var lastSection = "";
  private var sectionChange = true

  private var currLanguage = "none"
  private var currPartOfSpeech = "none"
  private var currRecord : List[Node]=  List()
  private var uri = "none" //store the subjectUri

  override def extract(page: PageNode, subjectUri: String, pageContext: PageContext): Graph =
  {
   uri = subjectUri
   //for DEBUG: wait a random time (this makes threading useless, but i can read the console output better)
   val r = new scala.util.Random
   //Thread sleep r.nextInt(10)*1000

   println("---------------")

   println(subjectUri)
   //dump(page)

   //iterate over the page with a sliding window of size 5 -> look ahead/back 2 nodes
   val buff = new MyBuffer[Node](page.children, 2)
   def nodeHandler(node : Node, window : List[Node], center : Int) = {
     sectionChange = false
     //dump(node)

     //skip nodes that carry no information (empty textnodes)
     if(node.isInstanceOf[TextNode] && node.asInstanceOf[TextNode].text.trim.isEmpty) {
       /*skip node*/
     } else {
       //check in which section we are
       checkSectionContext(node)

       //we use the line break as a separator of section elements
       //if a line break is contained in a TextNode, we need to split him
       //mostly the line break is at the end of the TextNode but sometimes not (when there is no LinkNode ([1],...) at the beginning of the line)
       if(node.isInstanceOf[TextNode]){
         if(node.asInstanceOf[TextNode].text.contains("\n")){
           val parts =  node.asInstanceOf[TextNode].text.split("\\n")

           //complete the current record
           handleSectionElement(flushSectionElementRecording :+ new TextNode(parts(0), node.line))

           //handle multiple records, that are contained in one TextNode
           if(parts.size > 2){
             for (i <- 1 until parts.size-2){
               handleSectionElement(List(new TextNode(parts(i), node.line)))
               //println("parts("+i+"):"+parts(i))
             }
           }

           //start recording from the last linebreak on
           startSectionElementRecording
           if(parts.size > 1){
             record(new TextNode(parts(parts.size -1), node.line))  //if the line break ist the very first or last character, splitting returns only one part
           }
           skipThisOneFromRecording = true   //we splitted this node and handled the parts, so recording is done
         }
       }

       //also we look for ending and starting sections, which implicitly end or start an element
       if(sectionChange){
         if(isRecordingSectionElement){
           handleSectionElement(flushSectionElementRecording)
         }
         startSectionElementRecording
       }

       //directly extract data from special nodes (language sections and part of speech sections)
       node  match  {
         case tn : TemplateNode => {
           if(tn.title.decoded == languageTemplates(language)){
             val possibleLanguage = extractLanguage(tn)
             if(possibleLanguage.isDefined){
               handleNewLanguage(possibleLanguage.get)
             }
           } else if(tn.title.decoded == partOfSpeechTemplates(language)){
             val possiblePartOfSpeech = extractPartOfSpeechFromTemplate(tn)
             if(possiblePartOfSpeech.isDefined){
               handleNewPartOfSpeech(possiblePartOfSpeech.get)
             }
           }
         }
         case sn : SectionNode => {
           val possiblePartOfSpeech = extractPartOfSpeechFromSecTitle(sn)
           if(possiblePartOfSpeech.isDefined){
             handleNewPartOfSpeech(possiblePartOfSpeech.get)
           }
         }
         case node : Node => {
         }
       }

       record(node)
     }
   }
   buff.iterateOverWith(nodeHandler)

   println()
   println()

   //TODO build triples

    new Graph()
  }

 private def record(node: Node): Unit = {
   //check if recording is really enabled
   if (isRecordingSectionElement && !skipThisOneFromRecording) {
     currRecord = currRecord :+ node
   }
   skipThisOneFromRecording = false
 }

 private def startSectionElementRecording(): Unit = {
   isRecordingSectionElement = true
 }

 private def flushSectionElementRecording(): List[Node] = {
   isRecordingSectionElement = false
   val rec = currRecord
   currRecord = List()
   rec
 }

 private def checkSectionContext(node : Node)   : Unit =
 {
   node match {
     case sn : SectionNode =>
     case tn : TemplateNode =>
     case _ => return
   }
   val sectionName : String = node match {
     case sn : SectionNode => sn.name
     case tn : TemplateNode => tn.title.decoded
   }
   try{
     if(translateSectionBack.apply(language).contains(sectionName) && currSection != translateSectionBack.apply(language).apply(sectionName)){
       lastSection = currSection;
       currSection = translateSectionBack.apply(language).apply(sectionName)
       sectionChange = true
       skipThisOneFromRecording = true  //
       //println("section "+currSection)
     }
   } catch {case _ => }  //catch sections that we dont listen for
 }

 /**
  * "in" is a list of nodes that were on one line
  */
 private def handleSectionElement(in : List[Node]) : Unit = {
   if(in.isEmpty) return

   val lineString = in.map(_.retrieveText.getOrElse("")).reduceLeft(_ + _)
   val lineStringFormatted = lineString.trim.replaceAll("^\\d+:\\s*","")
   if(lineStringFormatted.isEmpty) return

   val forSection = if(sectionChange) lastSection else currSection

   //example triple
   val subjectUri = "<"+uri+"#"+currLanguage+"-"+currPartOfSpeech+">"
   val property = "wiktionary:has" + forSection.capitalize
   var theObject = ""
   //TODO each possible section can handle their data here
   if(forSection == "meaning"){
     theObject += "\""
     for(i <- in.indices){
        if(!(i == 0 && in(i).isInstanceOf[TextNode] && in(i).asInstanceOf[TextNode].text == ":") &&
           !(i == 1 && in(i).isInstanceOf[ExternalLinkNode] && in(i).asInstanceOf[ExternalLinkNode].children(0).asInstanceOf[TextNode].text.matches("\\d+"))
        ){
           theObject += in(i).retrieveText.getOrElse("").trim.replaceAll("\"","\\\"") + " "//TODO fix escaping
        }
      }
     theObject += "\"@"+language
     println(subjectUri + " " + property +" "+ theObject)
   } else if (forSection == "example"){
     theObject += "\""
     for(i <- in.indices){
        if(!(i == 0 && in(i).isInstanceOf[TextNode] && in(i).asInstanceOf[TextNode].text == ":") &&
           !(i == 1 && in(i).isInstanceOf[ExternalLinkNode] && in(i).asInstanceOf[ExternalLinkNode].children(0).asInstanceOf[TextNode].text.matches("\\d+"))
        ){
           theObject += in(i).retrieveText.getOrElse("").trim.replaceAll("\"","\\\"") + " "//TODO fix escaping
        }
      }
     theObject += "\"@"+language
     println(subjectUri + " " + property +" "+ theObject)
   }//else if (forSection == "bla"){}

   //maybe return a set of rdf statements
 }

 private def handleNewLanguage(possibleLanguage: String): Unit = {
   currLanguage = possibleLanguage
   //println("currLanguage: " + currLanguage)
 }

 private def handleNewPartOfSpeech(possiblePartOfSpeech: String): Unit = {
   currPartOfSpeech = possiblePartOfSpeech
   //println("currPartOfSpeech " + currPartOfSpeech)
 }

 private def extractPartOfSpeechFromTemplate(tn : TemplateNode) : Option[String] =
 {
   if(tn.property("0").isEmpty || tn.property("1").isEmpty || tn.property("0").get.children(0).asInstanceOf[TextNode].text != languageTemplates(language)){
     return None
   }
   if(tn.property("2").isDefined){
     handleNewLanguage(tn.property("2").get.children(0).asInstanceOf[TextNode].text)
   }
   return Some(tn.property("1").get.children(0).asInstanceOf[TextNode].text)
 }

 private def extractPartOfSpeechFromSecTitle(sn : SectionNode) : Option[String] =
 {
   //TODO regard the new fix for unparsed section titles
   //at first i got an ugly workaround
   val tpl = sn.name.trim.replaceAll("\\{\\{","").replaceAll("\\}\\}","")
   val parts = tpl.split("\\|")
   if(parts(0) == "Wortart"){
     try{
       handleNewLanguage(parts(2))
     } catch {case _ => }
     return Some(parts(1))
   }
   //this wont really work but should look something like this
   //sn.children.foreach(
    //child=>if(child.isInstanceOf[TemplateNode] && child.asInstanceOf[TemplateNode].title.decoded == partOfSpeechTemplates(language))
    //{return Some(child.asInstanceOf[TemplateNode].property("1").get.children(0).asInstanceOf[TextNode].text)})
   None
 }

 private def extractLanguage(tn : TemplateNode) : Option[String] =
 {
   if(tn.property("0").isEmpty || tn.property("1").isEmpty || tn.property("0").get.children(0).asInstanceOf[TextNode].text != partOfSpeechTemplates(language)){
     return None
   }
   return Some(tn.property("1").get.children(0).asInstanceOf[TextNode].text)
 }

 private def dump(node : Node, depth : Int = 0) : Unit =
 {
   //indent by depth
   val prefix = " "*depth

   //print class
   println(prefix+node.getClass)

   //dump node properties according to its type
   if(node.isInstanceOf[TextNode]){
     println(prefix+"text : "+node.retrieveText)
   } else
   if(node.isInstanceOf[SectionNode]){
     println(prefix+"secname : "+node.asInstanceOf[SectionNode].name)
   }  else
   if(node.isInstanceOf[TemplateNode]){
     println(prefix+"tplname : "+node.asInstanceOf[TemplateNode].title.decoded)
   }

   //dump children
   println(prefix+"children:  {")
   node.children.foreach(
     child => dump(child, depth + 2)
   )
   println(prefix+"}")
 }
}

